﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml.Linq;
using Microsoft.Forums.OfflineStore;
using Microsoft.Forums.SyncService;
using Microsoft.Forums.Tests.HelperTools.Stubs;
using System.Globalization;
using Microsoft.Forums.ForumsProvider;

namespace Microsoft.Forums.Tests.HelperTools
{
    /// <summary>
    /// Provides functionality to parse xml files which represent data to initialize ForumClient database with
    /// 
    /// Create a new ForumDataCreator for each test
    /// </summary>
    public class ForumDataCreator
    {
        ForumsOfflineStore localStore;

        /// <summary>
        /// List of Provider stubs
        /// </summary>
        public IList<ForumsProviderStub> ProviderStubs { get; set; }

        /// <summary>
        /// List of provider stubs which have been cast to their underlying interface
        /// </summary>
        public IList<IForumServiceProvider> Providers
        {
            get
            {
                return ProviderStubs.Cast<IForumServiceProvider>().ToList();
            }
        }

        /// <summary>
        /// Create test data from xml file
        /// </summary>
        /// <param name="db">The data context</param>
        public ForumDataCreator(ForumsOfflineStore db)
        {
            if (db == null) throw new ArgumentNullException("db", "db object must not be null");

            localStore = db;
        }

        /// <summary>
        /// Given an xml data file parse it, turn it into the database object model
        /// then submit that data to the database
        /// 
        /// Also, create a collection of provider stubs for these added database items
        /// </summary>
        /// <param name="dataFile"></param>
        public void Load(string dataFile)
        {
            string dataDirectory = @"LocalData\";
            string testFile = null;

            if (File.Exists(dataFile))
            {
                testFile = dataFile;
            }
            else if (File.Exists(dataDirectory + dataFile))
            {
                testFile = dataDirectory + dataFile;
            }
            else
            {
                throw new FileNotFoundException(String.Format("Could not find {0}", dataFile));
            }

            ParseAndStoreData(testFile);
        }

        /// <summary>
        /// Read all pending actions from the xml files that belong to the 
        /// given ForumServiceProvider.  Then convert each into PendingAction object and return a 
        /// collection of them
        /// </summary>
        /// <param name="testDoc">An open xml document</param>
        /// <param name="pr">The parent provider of the forums to be retrieved</param>
        /// <returns>Collection of pending action objects objects</returns>
        private static IEnumerable<PendingAction> GetPendingActionsFromXml(XDocument testDoc, IForumServiceProvider pr)
        {
            if (testDoc == null) throw new ArgumentNullException("testDoc", "testDoc must not be null");
            if (pr == null) throw new ArgumentNullException("pr", "pr must not be null");

            var actions = from f in testDoc.Descendants("PendingAction")
                         where f.Parent.Attribute("Name").Value == pr.ProviderName
                         select new PendingAction
                         {
                             ActionStatus = (int)Enum.Parse(typeof(ActionStatus),f.Attribute("ActionStatus").Value,true),
                             ActionType = (int)Enum.Parse(typeof(ActionType),f.Attribute("ActionType").Value,true),
                             ThreadType = f.Attribute("ThreadType")!=null ? (int)Enum.Parse(typeof(ThreadType),f.Attribute("ThreadType").Value,true) : 0,
                             ForumServiceProviderId = pr.ProviderId,
                             Body = GetAttributeOrDefault<string>(f, "Body"),
                             Topic = GetAttributeOrDefault<string>(f, "Topic"),
                             Comment = GetAttributeOrDefault<string>(f, "Comment"),
                             ForumId = GetAttributeOrDefault<Guid>(f, "ForumId"),
                             ThreadId = GetAttributeOrDefault<Guid>(f, "ThreadId"),
                             PostId = GetAttributeOrDefault<Guid>(f, "PostId"),
                             OtherId = GetAttributeOrDefault<Guid>(f, "OtherId")
                              
            
                         };

            return actions.ToList();

        }

        /// <summary>
        /// Read all forum entries from the xml files that belong to the 
        /// given ForumServiceProvider.  Then convert each into Forum object and return a 
        /// collection of them
        /// </summary>
        /// <param name="testDoc">An open xml document</param>
        /// <param name="pr">The parent provider of the forums to be retrieved</param>
        /// <returns>Collection of forums objects</returns>
        private static IEnumerable<Forum> GetForumsFromXml(XDocument testDoc, IForumServiceProvider pr)
        {
            if (testDoc == null) throw new ArgumentNullException("testDoc", "testDoc must not be null");
            if (pr == null) throw new ArgumentNullException("pr", "pr must not be null");

            var forums = from f in testDoc.Descendants("Forum")
                     where f.Parent.Attribute("Name").Value == pr.ProviderName
                     let newId = Guid.NewGuid()
                     select new Forum
                     {
                         Id = newId,
                         ForumServiceProviderId = pr.ProviderId,
                         Name = f.Attribute("Name").Value,
                         ModifiedOn = DateTime.Now,
                         DisplayName = f.Attribute("Name").Value,
                         ForumPropertySet = new ForumPropertySet
                         {
                             ForumId = newId,
                             LastSyncedOn = DateTime.Now,
                             InitialSyncAge = GetAttributeOrDefault<int>(f,"InitialSyncAge")
                         }
                     };
            
            return forums.ToList();

        }

        /// <summary>
        /// Read all thread entries from the xml files that belong to the 
        /// given Forum.  Then convert each into Thread object and return a 
        /// collection of them
        /// </summary>
        /// <param name="testDoc">An open xml document</param>
        /// <param name="f">The parent forum of the threads to be retrieved</param>
        /// <returns>Collection of Thread objects</returns>
        private static IEnumerable<Thread> GetThreadsFromXml(XDocument testDoc, Forum f)
        {
            if (testDoc == null) throw new ArgumentNullException("testDoc", "testDoc must not be null");
            if (f == null) throw new ArgumentNullException("f", "f must not be null");

            var threads = from t in testDoc.Descendants("Thread")
                           where t.Parent.Attribute("Name").Value == f.Name
                           let newId = Guid.NewGuid()
                           let createdDate = GetAttributeOrDefault<DateTime>(t,"CreatedOn")
                           select new Thread
                           {
                               Id = newId,
                               ForumId = f.Id,
                               Topic = t.Attribute("Topic").Value,
                               CreatedOn = createdDate,
                               ModifiedOn = createdDate,
                               ThreadPropertySet = new ThreadPropertySet
                               {
                                   ThreadId = newId,
                                   LastSyncedOn = GetAttributeOrDefault<DateTime>(t,"CreatedOn")
                               }
                           };

            return threads.ToList();

        }


        /// <summary>
        /// Read all post entries from the xml files that belong to the 
        /// given Thread.  Then convert each into Post object and return a 
        /// collection of them
        /// </summary>
        /// <param name="testDoc">An open xml document</param>
        /// <param name="t">The parent thread of the posts to be retrieved</param>
        /// <returns>Collection of Post objects</returns>
        private static IEnumerable<Post> GetPostsFromXmlThread(XDocument testDoc, Thread t)
        {
            if (testDoc == null) throw new ArgumentNullException("testDoc", "testDoc must not be null");
            if (t == null) throw new ArgumentNullException("t", "t must not be null");

            var posts = from p in testDoc.Descendants("Post")
                        where p.Parent.Name == "Thread" && p.Parent.Attribute("Topic").Value == t.Topic
                        let newId = Guid.NewGuid()
                        select MakePostFromXml(p,t.ForumId, t.Id, null);

            var rootPosts = posts.ToList();
            List<Post> allPosts = new List<Post>();
            allPosts.AddRange(rootPosts);
            foreach (var post in rootPosts)
            {
                allPosts.AddRange(GetPostsFromXmlPost(testDoc, post, t));
            }

            return allPosts;

        }

 

        /// <summary>
        /// Read all post entries from the xml files that belong to the 
        /// given Thread.  Then convert each into Post object and return a 
        /// collection of them
        /// </summary>
        /// <param name="testDoc">An open xml document</param>
        /// <param name="t">The parent thread of the posts to be retrieved</param>
        /// <returns>Collection of Post objects</returns>
        private static IEnumerable<Post> GetPostsFromXmlPost(XDocument testDoc, Post parent, Thread thread)
        {
            if (testDoc == null) throw new ArgumentNullException("testDoc", "testDoc must not be null");
            if (parent == null) throw new ArgumentNullException("parent", "parent must not be null");

            var posts = from p in testDoc.Descendants("Post")
                        where p.Parent.Name == "Post" && p.Parent.Attribute("Subject").Value == parent.Subject
                        let newId = Guid.NewGuid()

                        select MakePostFromXml(p, thread.ForumId, thread.Id, parent.Id);

            var rootPosts = posts.ToList();
            List<Post> allPosts = new List<Post>();
            allPosts.AddRange(rootPosts);
            foreach (var post in rootPosts)
            {
                allPosts.AddRange(GetPostsFromXmlPost(testDoc, post, thread));
            }

            return allPosts;

        }

        /// <summary>
        /// Makes the post from XML.
        /// </summary>
        /// <param name="p">The p.</param>
        /// <param name="forumId">The forum id.</param>
        /// <param name="threadId">The thread id.</param>
        /// <param name="parentId">The parent id.</param>
        /// <returns></returns>
        private static Post MakePostFromXml(XElement p, Guid forumId, Guid threadId, Guid? parentId)
        {
            Guid newId = Guid.NewGuid();
            DateTime createdDate = GetAttributeOrDefault<DateTime>(p, "CreatedOn");
            return new Post
            {
                Id = newId,
                ThreadId = threadId,
                ParentId = parentId,
                ForumId = forumId,
                Subject = p.Attribute("Subject").Value,
                CreatedOn = createdDate,
                ModifiedOn = createdDate,
                CreatedBy = ForumObjectHelper.MakeNewUser(),
                PostPropertySet = new PostPropertySet
                {
                    PostId = newId,
                    IsRead = GetAttributeOrDefault<bool>(p, "IsRead")
                }
            };
        }

        /// <summary>
        /// Parse the xml file, store in database, update collection of provider stubs
        /// </summary>
        /// <param name="testFile"></param>
        private void ParseAndStoreData(string testFile)
        {
            if (testFile == null) throw new ArgumentNullException("testFile", "testFile must not be null");

            XDocument testDoc = XDocument.Load(testFile);
    
            IEnumerable<Forum> forums = null;
            IEnumerable<Thread> threads = null;
            IEnumerable<Post> posts = null;
            IEnumerable<PendingAction> actions = null;

            ProviderStubs = (from prov in testDoc.Descendants("ServiceProvider")
                         select new ForumsProviderStub
                        {
                            ProviderName_Result = prov.Attribute("Name").Value,
                            ProviderId_Result = Guid.NewGuid(),
                        }).ToList();

            foreach (var pr in ProviderStubs)
            {
                localStore.ForumServiceProviders.InsertOnSubmit(new ForumServiceProvider { DisplayName = pr.ProviderName, Id = pr.ProviderId_Result });

                actions = GetPendingActionsFromXml(testDoc, pr);
                forums = GetForumsFromXml(testDoc,pr);

                localStore.Forums.InsertAllOnSubmit(forums);
                localStore.PendingActions.InsertAllOnSubmit(actions);

                foreach (var f in forums)
                {
                    threads = GetThreadsFromXml(testDoc, f); 

                    localStore.Threads.InsertAllOnSubmit(threads);
                    
                    foreach (var t in threads)
                    {
                        posts = GetPostsFromXmlThread(testDoc, t);
                        localStore.Posts.InsertAllOnSubmit(posts);
                    }
                }
            }
            localStore.SubmitChanges();
        }

        /// <summary>
        /// Gets the attribute or default value.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="element">The element.</param>
        /// <param name="attibuteName">Name of the attibute.</param>
        /// <returns></returns>
        private static T GetAttributeOrDefault<T>(XElement element, string attibuteName)
        {
            T ret = default(T);
            try
            {
                XAttribute attribute = element.Attribute(attibuteName);

                if (attribute != null)
                {
                    ret = (T)Convert.ChangeType(attribute.Value, typeof(T));
                }
            }
            catch (InvalidCastException)
            {
            }
            catch (ArgumentNullException)
            {
            }

            return ret;
        }


    }
}
